#ifndef _DREFLECT_HPP_
#define _DREFLECT_HPP_

#include <string>
#include <tuple>
#include <unordered_map>

namespace Engine2D
{
    template<typename T, typename T2>
    struct FieldInfo
    {
        using Type = T;
        T2 ptr;
        size_t offset;
        std::string name;
        std::unordered_map<std::string, int> enums;
        
        template<typename U>
        const T & Value(U & object) {
            return *(T*)((size_t)&object + offset);
        }

        template<typename U, typename V>
        void Value(U & object, V value) {
            *(V*)((size_t)&object + offset) = value;
        }

        constexpr bool IsClass() {
            return std::is_class_v<Type>;
        }

        constexpr bool IsEnum() {
            return std::is_enum_v<Type>;
        }

        constexpr const std::vector<std::string> & Enums() const {
            return enums;
        }

        template<typename U>
        constexpr bool Is() {
            return std::is_same_v<Type, std::decay_t<U>>;
        }

        constexpr bool Reflectable() {
            Reflect<Type> info;
            return info.Reflectable();
        }
    };
    
    template<typename T>
    struct ReflectCRTP
    {
        template<typename F, size_t idx = 0>
        constexpr void Foreach(F && fn) {
            if constexpr (idx < std::tuple_size_v<decltype(static_cast<T*>(this)->m_fields)>) {
                fn(std::get<idx>(static_cast<T*>(this)->m_fields));
                Foreach<F, idx + 1>(std::forward<F>(fn));
            }
        }

        constexpr std::string Name() {
            return static_cast<T*>(this)->m_name;
        }   

        constexpr bool Reflectable() {
            return static_cast<T*>(this)->is_refEnabled;
        }

        constexpr int FieldSize() {
            return std::tuple_size_v<decltype(static_cast<T*>(this)->m_fields)>;
        }

        template<size_t idx>
        constexpr auto Get() {
            return std::get<idx>(static_cast<T*>(this)->m_fields);
        }

        template<typename F>
        constexpr void Visit(F && fn, const std::string & fieldName) {
            this->Foreach([&fieldName, fn = std::forward<F>(fn)](auto field) {
                if (field.name == fieldName) {
                    fn(field);
                }
            });
        }

    };

    template<typename T>
    class Reflect : public ReflectCRTP<Reflect<T>> 
    {
        template<typename U> 
        friend struct ReflectCRTP;
    private:
        constexpr inline static bool is_refEnabled = false; 
    };

    #define REF_ENABLE \
        template<typename T> \
        friend class Engine2D::Reflect; \

    #define REF_ENUM(enumClass, enum) {#enum, (int)enumClass::enum}

    #define REF_REGISTER_FIELD(className, fieldName) \
        FieldInfo<decltype(className::fieldName), decltype(&className::fieldName)>{&className::fieldName, offsetof(className, fieldName), #fieldName}

    #define REF_REGISTER_ENUM(className, fieldName, ...) \
        FieldInfo<decltype(className::fieldName), decltype(&className::fieldName)>{&className::fieldName, offsetof(className, fieldName), #fieldName, {__VA_ARGS__}}


    #define REF_REGISTER_CLASS(className, ...) \
        template<> \
        class Reflect<className> : public ReflectCRTP<Reflect<className>> { \
        private: \
            template<typename T> \
            friend struct ReflectCRTP; \
            constexpr inline static bool is_refEnabled = true; \
            inline static std::string m_name = #className; \
            inline static auto m_fields = std::make_tuple(__VA_ARGS__); \
        };
} // namespace Engine2D

#endif